{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import torchtext\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from torch.autograd import Variable\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from torchtext.vocab import Vectors\n",
    "from tqdm import tqdm_notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/sob/anaconda2/lib/python2.7/site-packages/nltk/tree.py:623: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal\n",
      "  elif token == close_b:\n",
      "/Users/sob/anaconda2/lib/python2.7/site-packages/nltk/tree.py:616: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal\n",
      "  if token[0] == open_b:\n"
     ]
    }
   ],
   "source": [
    "text = torchtext.data.Field(include_lengths = True)\n",
    "label = torchtext.data.Field(sequential=False)\n",
    "train, val, test = torchtext.datasets.SST.splits(text, label, filter_pred=lambda ex: ex.label != 'neutral')\n",
    "text.build_vocab(train)\n",
    "label.build_vocab(train)\n",
    "train_iter, val_iter, test_iter = torchtext.data.BucketIterator.splits((train, val, test), batch_size=10, device=-1, repeat = False)\n",
    "url = 'https://s3-us-west-1.amazonaws.com/fasttext-vectors/wiki.simple.vec'\n",
    "text.vocab.load_vectors(vectors=Vectors('wiki.simple.vec', url=url))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class CNN_CBoW(nn.Module):\n",
    "    def __init__(self, in_channels, out_channels, batch_size, ):\n",
    "        super(CNN_CBoW, self).__init__()\n",
    "        self.nonstatic_embeddings = nn.Embedding(text.vocab.vectors.size()[0], text.vocab.vectors.size()[1])        \n",
    "        self.nonstatic_embeddings.weight.data.copy_(text.vocab.vectors)\n",
    "        self.embeddings = self.nonstatic_embeddings # this is our default. They are pre-initialized, then update based on the problem.\n",
    "        \n",
    "#         self.static_embeddings = self.nonstatic_embeddings\n",
    "#         self.static_embeddings.weight.requires_grad = False\n",
    "#         self.embeddings = self.static_embeddings\n",
    "        \n",
    "#         self.random_static_embeddings = nn.Embedding(text.vocab.vectors.size()[0], text.vocab.vectors.size()[1]) # no lookup\n",
    "#         self.random_static_embeddings.weight.requires_grad = False\n",
    "#         self.self.embeddings = self.random_static_embeddings\n",
    "        \n",
    "#         self.random_nonstatic_embeddings = nn.Embedding(text.vocab.vectors.size()[0], text.vocab.vectors.size()[1]) # no lookup\n",
    "#         self.embeddings = self.random_nonstatic_embeddings\n",
    "        \n",
    "        \"\"\" for multichannel, we replace embedded = self.embeddings(x) in self.forward with\n",
    "            embedded = torch.cat([self.static_embeddings(x), self.nonstatic_embeddings(x)], dim = 1)\n",
    "            and increase the corresponding input dimension of the proceeding layer at initialization. \n",
    "            In this model, that would involve letting in_channels = in_channels*2 \"\"\"\n",
    "        \n",
    "        self.convs = nn.ModuleList([nn.Conv1d(in_channels = in_channels, out_channels = in_channels, kernel_size = n) for n in (1,2,3,4)])\n",
    "        self.dropout_train, self.dropout_test = nn.Dropout(p = 0.5), nn.Dropout(p = 0)\n",
    "        self.linear = nn.Linear(in_features=in_channels*2 + 1, out_features=out_channels, bias = True)\n",
    "    \n",
    "    def forward(self, x, train = True):\n",
    "        x, lengths = x\n",
    "        lengths = Variable(lengths.view(-1, 1).float())\n",
    "        embedded = self.embeddings(x)\n",
    "        average_embed = embedded.mean(0)\n",
    "        cbow = torch.cat([average_embed, lengths], dim = 1)\n",
    "        \n",
    "        embedded = embedded.transpose(1, 2)\n",
    "        embedded = embedded.transpose(0, 2)\n",
    "        concatted_features = torch.cat([conv(embedded) for conv in self.convs if embedded.size(2) >= conv.kernel_size[0]], dim = 2)\n",
    "        activated_features = torch.nn.functional.relu(concatted_features)\n",
    "        pooled = torch.nn.functional.max_pool1d(activated_features, activated_features.size(2)).squeeze(2)\n",
    "        \n",
    "        ensemble = torch.cat([pooled, cbow], dim = 1)\n",
    "        \n",
    "        dropped = self.dropout_train(ensemble) if train else self.dropout_test(ensemble)\n",
    "        output = self.linear(ensemble)\n",
    "        logits = torch.nn.functional.log_softmax(output, dim = 1)\n",
    "        return logits\n",
    "\n",
    "    def predict(self, x):\n",
    "        logits = self.forward(x, train = False)\n",
    "        return logits.max(1)[1] + 1\n",
    "    \n",
    "    def train(self, train_iter, val_iter, test_iter, num_epochs, learning_rate = 1e-3):\n",
    "        criterion = torch.nn.NLLLoss()\n",
    "        optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)\n",
    "        loss_vec = []\n",
    "        \n",
    "        for epoch in tqdm_notebook(range(1, num_epochs+1)):\n",
    "            epoch_loss = 0\n",
    "            for batch in train_iter:\n",
    "                x = batch.text\n",
    "                y = batch.label\n",
    "                \n",
    "                optimizer.zero_grad()\n",
    "                \n",
    "                y_p = self.forward(x)\n",
    "                \n",
    "                loss = criterion(y_p, y-1)\n",
    "                loss.backward()\n",
    "                \n",
    "                optimizer.step()\n",
    "                epoch_loss += loss.data[0]\n",
    "                \n",
    "            self.model = model\n",
    "            \n",
    "            loss_vec.append(epoch_loss / len(train_iter))\n",
    "            if epoch % 1 == 0:\n",
    "                acc = self.validate(val_iter)\n",
    "                print('Epoch {} loss: {} | acc: {}'.format(epoch, loss_vec[epoch-1], acc))\n",
    "                self.model = model\n",
    "                self.test(test_iter)\n",
    "        \n",
    "        plt.plot(range(len(loss_vec)), loss_vec)\n",
    "        plt.xlabel('Epoch')\n",
    "        plt.ylabel('Loss')\n",
    "        plt.show()\n",
    "        print('\\nModel trained.\\n')\n",
    "        self.loss_vec = loss_vec\n",
    "        self.model = model\n",
    "\n",
    "    def test(self, test_iter):\n",
    "        \"All models should be able to be run with following command.\"\n",
    "        upload, trues = [], []\n",
    "        # Update: for kaggle the bucket iterator needs to have batch_size 10\n",
    "        for batch in test_iter:\n",
    "            # Your prediction data here (don't cheat!)\n",
    "            x, y = batch.text, batch.label\n",
    "            probs = self.predict(x)\n",
    "            upload += list(probs.data)\n",
    "            trues += list(y.data)\n",
    "        correct = sum([1 if i == j else 0 for i, j in zip(upload, trues)])\n",
    "        accuracy = correct / len(trues)\n",
    "        print('Testset Accuracy:', accuracy)\n",
    "        \n",
    "        with open(\"predictions.txt\", \"w\") as f:\n",
    "            for u in upload:\n",
    "                f.write(str(u) + \"\\n\")\n",
    "                \n",
    "    def validate(self, val_iter):\n",
    "        y_p, y_t, correct = [], [], 0\n",
    "        for batch in val_iter:\n",
    "            x, y = batch.text, batch.label\n",
    "            probs = self.model.predict(x)[:len(y)]\n",
    "            y_p += list(probs.data)\n",
    "            y_t += list(y.data)\n",
    "        \n",
    "        correct = sum([1 if i == j else 0 for i, j in zip(y_p, y_t)])\n",
    "        accuracy = correct / len(y_p)\n",
    "        return accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c719470e87e043a4818ae3d46f91c430",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "A Jupyter Widget"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Exception in thread Thread-4:\n",
      "Traceback (most recent call last):\n",
      "  File \"/Users/sob/anaconda2/lib/python2.7/threading.py\", line 801, in __bootstrap_inner\n",
      "    self.run()\n",
      "  File \"/Users/sob/anaconda2/lib/python2.7/site-packages/tqdm/_tqdm.py\", line 144, in run\n",
      "    for instance in self.tqdm_cls._instances:\n",
      "  File \"/Users/sob/anaconda2/lib/python2.7/_weakrefset.py\", line 60, in __iter__\n",
      "    for itemref in self.data:\n",
      "RuntimeError: Set changed size during iteration\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1 loss: 0.653699348858 | acc: 0\n",
      "('Testset Accuracy:', 0)\n",
      "Epoch 2 loss: 0.527836714392 | acc: 0\n",
      "('Testset Accuracy:', 0)\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-7-79dd0061091e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mCNN_CBoW\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0min_channels\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mout_channels\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbatch_size\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrain_iter\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrain_iter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mval_iter\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mval_iter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest_iter\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_iter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_epochs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m12\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlearning_rate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m1e-4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      3\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtest_iter\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-6-f1ca0575470b>\u001b[0m in \u001b[0;36mtrain\u001b[0;34m(self, train_iter, val_iter, test_iter, num_epochs, learning_rate)\u001b[0m\n\u001b[1;32m     68\u001b[0m                 \u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbackward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     69\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 70\u001b[0;31m                 \u001b[0moptimizer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     71\u001b[0m                 \u001b[0mepoch_loss\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     72\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/Users/sob/anaconda2/lib/python2.7/site-packages/torch/optim/adam.pyc\u001b[0m in \u001b[0;36mstep\u001b[0;34m(self, closure)\u001b[0m\n\u001b[1;32m     68\u001b[0m                 \u001b[0;31m# Decay the first and second moment running average coefficient\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     69\u001b[0m                 \u001b[0mexp_avg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmul_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbeta1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mbeta1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgrad\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 70\u001b[0;31m                 \u001b[0mexp_avg_sq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmul_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbeta2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maddcmul_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mbeta2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgrad\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgrad\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     71\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     72\u001b[0m                 \u001b[0mdenom\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexp_avg_sq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msqrt\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgroup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'eps'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "model = CNN_CBoW(in_channels = 300, out_channels = 2, batch_size = 10)\n",
    "model.train(train_iter = train_iter, val_iter = val_iter, test_iter = test_iter, num_epochs = 12, learning_rate = 1e-4)\n",
    "model.test(test_iter)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
